![]() ![]() |
IntroductionWhy do these guidelines exist?These guidelines serve a similar purpose to the Human Interface Guidelines: to promote basic design principles and consistency in the scripting interface of all Macintosh applications. The design principles are all about being task-specific; the more closely you match the user’s mental model of your application, the more useful it will be. Consistency in the scripting interface is valuable for the same reasons it is in the regular interface: it makes it easier for users to learn new applications and decreases the chances that they will make mistakes by following old habits, which in turn reduces the number of support calls you will have to field. Additionally, consistency among applications promotes AppleScript as a coherent system, not just a thin bit of glue holding together a hodgepodge of what are practically different languages. By following these guidelines, you can make your users happier, your own life easier, and help promote AppleScript as a useful tool. What is scripting?Scripting is programming using a scripting language.
That wasn’t very helpful: what is a scripting
language? A scripting language is a task-specific programming
language, as opposed to a systems programming language such as C, C++,
or Java. Systems programming languages are designed to build applications
from the ground up and are very general purpose – you can
build anything, but you also have to build everything, often
out of very small pieces. Scripting languages, on the other hand, are
designed with a particular task in mind. They can express things
appropriate to the task very easily, but are awkward or even mute on
other matters. For example, Being task-specific is a good thing for several reasons. Not only does it let you write less code, but humans do much better at cognitive tasks (like programming) when the content is set in a familiar domain. Therefore, a publisher who is bewildered by pointers and bit arithmetic would still be able to write a complex script that works in terms of text flows and illustrations. When thinking about what is task-specific, it is helpful to consider not only what the task is, but also what it is not. For example, the publisher above is concerned with box measurements and kerning, but does not want to be concerned with memory management or declaring variable types. Accordingly, scripting languages tend to allocate and deallocate memory automatically, not require variable types, and generally take care of a lot of the details that a systems programming language would require you to deal with. All this focus and convenience comes at a cost, of course: some scripting languages are wildly inappropriate for some problems, and programs written in a scripting language generally run slower than an equivalent one written in a systems programming language. On the other hand, the scripter might not be able to write the faster version at all, or might simply not have the time, and an unwritten program runs only as fast as a human can do the task by hand. For a complementary take on this subject, see John Ousterhout’s
paper What is scriptability?Scriptability is the ability of your application to be controlled by a script – that is, a program – rather than a person armed with a keyboard and mouse. By making your application scriptable, users can customize it, tie it into automated workflows, and generally make it more useful to themselves in ways that were too specific for you to bother with, or that you never even imagined in the first place. There are several ways to approach scriptability, but the most interesting and useful one is to expose your application’s model layer. This term comes from the model-view-controller (MVC) design pattern: the model layer holds the application data and logic for operating on it; the view layer presents that data in a visible form, say in a window; and the controller layer mediates between them, updating the view as the model changes and updating the model as the user manipulates the view. For a full description, see The Model-View-Controller Design Pattern. Exposing the model rather than the view has two key advantages. First, the model is generally more stable than the view. As you produce new versions of your application, buttons may move and commands may change, but what your application actually does is relatively constant. If scripting is in terms of the model, then existing scripts and scripters’ knowledge stay valid. Second, the model is more useful, because it has to do with the actual task, rather than how to do it. For example, if you want to change the name of a file in the Finder, you can say to set the name of that file, rather than describing the view-oriented steps: select the file, click on the name part, and type some characters. What is AppleScript?AppleScript is a scripting language created by Apple Computer, and is the primary language for controlling scriptable Macintosh applications. It has an English-like syntax, is strongly object-oriented, and has several features that make it uniquely well-suited to working with large collections of objects. For full details, see the AppleScript Language Guide. AppleScript approaches the problem of being task-specific in an unusual way. The base language does very little, and has no task-specific functions at all. What it does do is provide a framework into which you can plug any number of task-specific functions, namely applications. Depending on which object you are referring to, the vocabulary changes. For example, when talking to the Finder, you can speak of disks, folders, and files; when talking to Mail, you can speak of messages and mailboxes; and so on. In other words, applications are responsible for providing task-specific functions for the tasks that they know about; AppleScript provides the basic infrastructure and glue to pass information from one application to another. BasicsYour application’s scripting interface is advertised to the rest of the world, both the system and human users, by your dictionary. It lists all the objects and commands your application understands, along with how they are related and simple explanations of what they mean. AppleScript reads it to determine how to compile scripts, and it is the primary reference for scripters who want to know what your application can do. (This is not to say that it should be the only reference you provide. It shouldn’t; ideally, you will also provide documentation and sample scripts. The dictionary is required, though.) This section describes some general guidelines for creating your dictionary. Later sections contain specific guidance on particular topics such as objects or commands. Opinion varies on whether you should write your whole dictionary first and then implement it, or write and implement iteratively bit by bit. Both have their advantages – the former makes it easier to be consistent, the latter makes it easier to ship at any time – choose whichever works for you. Dictionaries currently come in three different formats:
To see your dictionary as a user would, open it in Script Editor: launch Script Editor, choose Open Dictionary from the File menu, and select your application. If you are new to scripting, examine some other dictionaries. The Finder is a good example: it follows the guidelines reasonably well, and is complex enough to be interesting, but simple enough to be comprehensible. ![]() Figure 1. Part of the Finder dictionary Before You BeginThink about scriptability earlier rather than later.Thinking about how to make your application scriptable early in the design process will save you considerable time and agony. First, it is always easier to implement a feature that is planned for from the beginning than to bolt it on at the end. Second, and more importantly, scriptability forces you to think about your model. If you know what your conceptual model objects are, you can create implementation objects that match. This not only makes scriptability easier, it’s just a good design practice. Know your model.Scriptability means being task-specific, and being task-specific means revealing the things your application does: that is, your model. Therefore, it is essential that you have a firm grasp of your application model. What are the objects and commands that define what your application does, and how do they relate to each other? For example, Mail has mailboxes, mailboxes contain messages, messages have addressees and text and can be sent or forwarded, and so on. Notice that this says nothing about buttons, menu commands, or dialogs. Remember: model, not view. If you can’t figure out what your model is, or think you don’t have one, you’re probably either looking at it the wrong way or your application design needs work. Talking to your users can be helpful here – ask them what they do, and listen for the common objects and commands. Know your users.Being task-specific means being user task-specific, not programmer task-specific. Think of your application in terms of things users do with it, not in terms of how you, the implementor, make things happen behind the scenes. For any object or command in your dictionary, it should be immediately obvious to a normal user of your application what it is. If you have a very large and complicated application, knowing your users is also helpful in prioritizing what to make scriptable first. You are not obliged to do it all at once. Know AppleScript.You do not have to learn AppleScript in every detail, but you should develop a feel for typical application command syntax, and for what AppleScript will do for you (and you therefore do not have to do yourself). The AppleScript Language Guide is the official Apple documentation for AppleScript, but there are a number of third-party books available as well. Playing with a scriptable application that follows the guidelines, such as the Finder, is also a good way to learn. Use an application framework.A large part of implementing scriptability properly involves implementing standard behavior. Using a framework will eliminate much of the drudgery and reduce the number of errors (or at least ensure that you have the same errors as everyone else using the framework). Apple’s Cocoa and Metrowerks’ PowerPlant both have good support for scriptability. Designing Your DictionaryStay on-task.Yes, we’ve said it before, but it’s important. Good
scriptability is focused on tasks that are meaningful to the user. If you find
yourself talking about error codes, bitfields, or other things that the user
does not normally see, then you are drifting off-task. Also remember that tasks
are based on the model – that is, the what – not
the view, which is the how. If you find yourself talking about buttons
and menus (and you are not working on an interface-building application), then
you are attempting to script the view. Some view scripting is
important – see Scripting the
View – but it is not the focus. If you have trouble thinking
of If you find yourself having trouble expressing your model in a way a user would consider sane, it may be a sign that your model needs fixing. (It may also be a sign that you need a break, so try that, too.) This happens sometimes: viewing your model from a different angle can reveal problems that were not apparent before. Perhaps one complex operation needs to be split, or two merged, or maybe you didn’t understand the task domain as well as you thought you did. Go ahead and fix it, and fix it in your graphical interface too, if you can – you’ll have a better application for it. Be object-oriented.Being object-oriented means that your scripting is mostly organized around objects, not commands. Objects can respond to commands – documents can be printed, messages can be sent, and so on – but the focus is on the objects, not the commands. This has several benefits: First, it matches how the rest of AppleScript, and to some extent the Macintosh as a whole, works. It is therefore more consistent and makes more sense to users. Second, it lets you exploit various features of AppleScript, like
Third, it lets you create a smaller, more comprehensible dictionary,
because you can apply a multiplicative effect: n commands times
m objects yields n×m actions, but
using only n+m terms. Consider an application that
defines a Be accurate.Make sure you actually support everything you claim to in your
dictionary. Be conservative in your dictionary if you need to; sdef
provides a way to hide
terms if you feel they are not yet ready for prime time. (So does the
older On a similar point, make sure that the types your dictionary defines
for properties and parameters are accurate. Bear in mind that Don’t re-invent the wheel.The less you invent, the more consistent you will be, and the less your users will need to learn.
A corollary to this is that you will probably not need to define many of
your own commands. The standard commands take care of basic application
functions and object manipulation ( It’s OK to do less than your GUI.Some operations possible through your graphical user interface may not be appropriate for a scripting interface, because they assume a level of dynamic interaction that simply isn’t practical – for example, freehand sketching, or scrubbing through a movie. Do not feel obligated to include a function in your scripting interface just because it exists in your graphical interface. Also, it may not be practical to make everything in your application scriptable all at once: you don’t have the time, you expect a feature to change in the next release, whatever. Provide what you can, make it useful, and do the rest later. It’s OK to do more than your GUI.Scriptability is a great way to expose features in your application without
having to implement a graphical interface for them. Sometimes these features
are implicit in how AppleScript works: for example, the Finder has no command to
delete files that meet a condition, but the Get feedback.Find some users who care about scripting, let them play with your application, and listen to what they say. You may find some on staff already – testers tend to be fond of scripting, because it makes their jobs easier. When You Think You’re DoneTest, test, test.Scriptability needs testing just like any other feature. Knowledge of AppleScript will be very helpful here, not just so you can write scripts, but so you can truly exercise the language. Newcomers often do not grasp the implications of object specifiers, and create commands that do not work with ranges or filter clauses. Get more feedback.Internal development and testing is all well and good, but nothing substitutes for real-world usage. Let real users try your scriptable application. If you discover a mistake in your scriptability after you ship, you can still change it without breaking existing scripts by exploiting synonyms – see the sdef man page for details. StyleThis section describes style guidelines for your dictionary: organization, how to name things, comment style, and so on. GeneralUse suites as appropriate.Dictionaries are broken into suites, which are clusters of related classes and commands. These are purely for the reader’s benefit, and have no impact on your application or AppleScript. There is no technical limit, either upper or lower, on the number of items in a suite, but ten to fifteen is considered a comfortable size. However, do not force artificial distinctions just to break up a large suite or combine small ones – the ultimate point is to help the user find things. Put system-defined suites first, followed by your own. Sort them in order of generality or frequency of use, or, failing that, alphabetically. Within a suite, and for properties in a class, order items in some sensible manner. For a sufficiently small group, you may sort by importance or frequency of use, but this can be hard to judge. Alphabetical ordering is always a good fallback. Within a command, order the parameters in a way that makes sense in a sentence. If this is impossible, use alphabetic order. Be accurate with types.When specifying the type of a property or parameter, use the most specific type that is both accurate and meaningful to a user. Often, this is a simple choice of a single application-defined class or a primitive data type, but sometimes it’s more interesting:
Things that refer to objects should be objects.If a property or parameter refers to an object, its type should be an actual object, never data that signifies an object. Consider this command from many web browsers that breaks this guideline (and several others): CloseWindow The correct way to do this is to define a A few words about files Choosing Language ElementsProperties vs. elementsObjects may contain other objects; these are either named properties
( Properties vs. commandsSometimes setting a property can cause an immediate change on the screen. In deciding whether to use a property in this situation, a helpful rule is: When an action is initiated, use a command; when an attribute changes (even if it produces immediate visible results), use a property. Another way of looking at this is if a visible change is immediate, use a property, but if an action has a duration, use a command. For example, the following command causes an immediate change on the screen: set the font of the third paragraph to "Courier" Even though setting the font property creates a visible change, the font is still an attribute of the text, not an action. On the other hand, naming a property or enumerator set playing to true set [the] status to playing The play the movie "Wowie Zowie" start playing the movie "Wowie Zowie" Note that the commands are EnumerationsAn enumeration is a set of constants (enumerators) which represent a fixed set of choices. Use an enumeration as the type of a parameter or property whenever there is a choice to be made from a specific list of possibilities. This helps the user see that there is a choice, and lets them know exactly what the choices are.
Naming RulesUse user terms, not implementation terms.Remember that you are speaking to a user, and should therefore use the terms they will find familiar. Ideally, the terms used in your graphical interface, scripting, and documentation should all match. Use the
|
BAD | Title |
GOOD | title |
OK | Macintosh, IEEE |
If a term consists of more than one word, separate the words with spaces. Do not join the words together with capital letters or underscores. Again, trade names are an exception to this.
BAD | TransferProtocol, network_address |
GOOD | transfer protocol, network address |
OK | AppleTalk |
Each word in a term must be a valid AppleScript identifier. That means they must begin with a letter or underscore, and be followed by any number of letters, numbers or underscores, the same as C-like languages. Punctuation is not allowed; underscores are not recommended as mentioned above.
Creating terms that look like other parts of speech will confuse scripters
and lead them to try things that will not work. Consider the Standard Additions
command set the clipboard to
: seeing it in a
script, you might think that it is a set
command operating on a
clipboard
property, but it really isn’t, so the supposedly
equivalent copy x to the clipboard
would not work.
Starting property names with verbs leads to confusion when the property
appears in the middle of a sentence. For example, naming a property
disable call waiting
leads to commands that do not read
smoothly:
set disable call waiting to true if disable call waiting then ...
Also, the name disable call waiting
looks like a command. A user
might be tempted to say
disable call waiting
This will compile and run, but does not disable anything – it merely returns the current value of the property. You can prevent such confusion by using a participle instead. This is somewhat clearer:
set call waiting enabled to false if not call waiting enabled ...
Even better would be to name the
property call waiting
and use an enumeration as its value type.
The choices enabled
and disabled
allow grammatically
correct sentences:
set call waiting to enabled if call waiting is disabled ...
Using a language keyword as the first word in a term may make it
impossible to use that keyword in certain contexts. For example, consider
an application that defines a mailbox
class that contains
message
objects. Adding an application property named
in mailbox
would prevent scripters
from using the keyword in
as a synonym for of
in some cases: messages in mailbox 1
would be parsed as
messages
in mailbox 1
, which is illegal, instead
of the intended messages
in mailbox 1
.
It is possible to successfully begin terms with keywords – consider
the Standard Additions command set the clipboard to
– but
it is not recommended. Terms may use language keywords in the middle or at
the end of terms, but see some of the following guidelines.
Reserved words in AppleScript 1 are
after | does | get | my | second | to |
and | eighth | given | ninth | set | transaction |
as | else | global | not | seventh | true |
back | end | if | of | sixth | try |
before | equal | ignoring | on | some | until |
beginning | equals | in | or | tell | where |
behind | error | into | prop | tenth | while |
but | every | is | property | that | whose |
by | exit | it | put | the | with |
considering | false | its | ref | then | without |
contain | fifth | last | reference | third | |
contains | first | local | repeat | through | |
continue | fourth | me | return | thru | |
copy | from | middle | returning | timeout | |
div | front | mod | script | times |
is
in Boolean property and parameter names.This is a specific case of the previous rule: is
is a reserved
word meaning is equal to.
For example, instead of is
minimized
, is encrypted
, or is busy
,
use minimized
, encrypted
, and busy
.
Also, the AppleScript parser
pulls a few special tricks on Boolean properties and parameters that make
them read better without the is
.
AppleScript will change true
and false
in Boolean parameters to with
and without
. For
example, if a scripter writes
send message "Fred" queuing true
it compiles to
send message "Fred" with queuing
Also, tests of Boolean properties can be written specially: instead of
saying property of object is true (or false)
,
you can say object is (or is not) property
.
For example, the following two lines are equivalent:
if miniaturized of window 1 is true then ... if window 1 is miniaturized then ...
If the property were named is miniaturized
, the second line would
have to be written if window 1 is is miniaturized
, which is awkward.
Notice that these special forms read best if the property name is simply an
adjective, not an adjectival phrase such as has x
or
wants x
.
of
in a name.Of
is a reserved word used between parts of an object specifier.
AppleScript will let you get away with this, but it can be very confusing
to read, because it makes the term look like a property of an object, which
it isn’t.
end
as the first word in a command.Several AppleScript constructs use end
to end a block: end
if
, end repeat
, and so on. In these cases, AppleScript will
fill in the word after the end
automatically. Users may be tempted
to try the same thing with your command, and it won’t work.
BAD | begin animating, end animating |
GOOD | start animating, stop animating |
Any dictionary term can have an associated comment (sdef’s and suite
terminology’s description
attributes, or
aete
’s comment fields). Use them to help clarify how your
vocabulary is to be used.
Since your dictionary is often the initial window
through which a user
looks to figure out what to do, descriptive comments can make the user’s
task a lot easier. Remember that your users are not necessarily programmers, so
avoid implementation terms in your comments.
For Boolean parameters and properties, if there are two possible states,
include a description of the true and false conditions, such as true if
the script will modify the original images, false if the images will be left
alone.
If the possible states are on and off, you need only include the true
condition (If true, then screen refresh is turned on
) or ask a
question starting with is
or does
(Is the window zoomed?
).
For enumerations, include a general description of what the parameter
or property represents; the individual enumerators should be
self-explanatory. For example, yes | no | ask -- Specifies whether or not
changes should be saved before closing.
Don’t use the comment field to explain a set of possible numeric
values when an enumeration (with descriptive enumerators) is better.
Instead of 0=read, 1=unread, …
use read | unread | …
If you allow more than one type (see sdef’s type="T1 | T2 |
..."
), include a description of the choices
for the types listed: the connection’s window (either a reference
or name can be used).
If the parameter or property has a default value (used when
the user doesn’t include an optional parameter or set the property),
mention it. This applies to values of any type: replacing
yes | no | ask -- Replace the file if it exists? (defaults to ask).
Because AppleScript is so object-centric, defining a sensible object
and class hierarchy and making it behave in all the standard ways is perhaps
the most important part of creating a good scripting interface for your
application. To paraphrase Fred Brooks slightly: Show me your code and
conceal your objects, and I shall continue to be mystified. Show me your
objects, and I won’t usually need your code; it will be obvious.
As always, the basic rule is be task specific. Reveal objects that have a direct bearing on things the user will do with your application. Expose the model, not the interface. Do not reveal implementation details – users will not care, or worse, it will confuse them.
There are two different hierarchies to consider: the inheritance hierarchy – what behaves like what – and the containment hierarchy – who owns what. Make your hierarchies match what a user would expect.
AppleScript supports single inheritance, so you can define a class to be
just like another class, but with additional properties or elements. A
subclass does not have to define any new parts; sometimes just being
a different class is enough. Mail’s recipient
classes
do this.
Use inheritance to effectively shrink your dictionary by removing redundant information. If several classes share common elements or properties, make them all inherit from a base class that defines the common parts. Do not use inheritance if a class has only one descendant, because that just complicates the dictionary.
AppleScript defines two kinds of containment relationships: elements and properties. In entity-relationship modeling terms, an element is a to-many relationship, and a property is either an attribute or a to-one relationship. (Entity-relationship modeling distinguishes between primitive data and other objects; AppleScript does not.) A property is a single value, while there may be any number of elements. Special cases such as zero-or-one or any-number-in-a-range can be emulated using properties or elements with additional behavior, as shown here:
how many? | use | example |
exactly one | property | name |
any number | element | documents |
zero or one | property, value may be missing value |
current printer |
exactly n, n > 1 | element, but can’t make or delete |
domains of application "System Events" |
In general, prefer an element to a property whose value is a list of objects – it is more consistent and is easier to implement. In particular, do not do something like this:
class widget (pl. widgets)...
class box:
property widgets: list of widget
This is especially evil, because it makes widget
look like an
element of box
when it really isn’t. For example,
widgets of box 1
works, and looks like an every
specifier,
but the supposedly equivalent every widget of box 1
will fail,
because it was really a property specifier.
If you do implement such a property (see the standard selection
property for an example), make sure that it works exactly as a normal container
in object specifiers – element subclasses, filters, and
so on should all work.
All objects in an application, aside from the root application
object, are contained by some other object. To select a particular object or
set of objects, a script uses an object specifier, or
specifier for short, which is an expression of the form
object-expression of container
.
Container is itself an object specifier, so the application
recursively works its way
up the containment chain until it reaches the (often implied) root
application
object, which contains everything. For example:
length of word 1 of every paragraph of document "Smith Project"
The underlined portions show each component. Notice that each component uses a different key form. The complete set of key forms is listed in the table below. Most key forms imply the existence of a particular property for that object: if the object has that property, then it should support that key form, and vice versa.
key form | property | example |
arbitrary | — | some file of the startup disk |
every | index | every word of document 1 |
filter | — | every document whose name starts with "Smith" |
ID | id | application file id "com.apple.finder" |
index | index | word 3 |
middle | index | middle shape of drawing 1 |
name | name | folder "Bob" |
property | — | name of document 1 |
range | index | words 3 through 8 |
relative | index | word after word 4 |
Additionally, allowable key forms for a container are determined by the
nature of the container and its elements. This also determines what locations,
if any, are meaningful for the make
and move
commands.
There are, broadly, three types of containers:
In an ordered container, the user determines exactly how
the items are arranged. Any item can be placed before (or after) any
other item using make
or move
, and it will stay there.
Paragraphs of a text document
are ordered, as are items in an AppleScript list.
In an unordered container, the data itself determines the order of the items – for example, alphabetical order – or there simply is no particular order. While you can ask for the nth item, iterate through all of them, or speak of the item before or after another one, asking to make an item at or move it to a particular position is meaningless – all you can do is ask to put it in the container. People in Address Book, files in the Finder, and items in an AppleScript record are all unordered.
In an uncounted container, the application does not know how many items
there are. All index forms are meaningless, as is the idea of every
.
To get an item, you must know something else about it: its name, its id, some
property, or similar. Web servers on the Internet are uncounted.
container type | example | access by | make at |
ordered | an AppleScript list | indexed, relative, arbitrary | indexed, relative, container |
unordered | an AppleScript record, people in Address Book |
indexed, relative, arbitrary | container |
uncounted | all Web servers on the Internet | — | container |
This section gives behavior details for each of the key forms. It does not give syntax or usage details; for that, see Chapter 5 of the AppleScript Language Guide.
If you support this key form, it should return a genuinely random object,
not always the same one. This key form is useful whenever a script needs
random behavior. For example, a script could
select a random signature in Mail by saying some signature
,
rather than the more complicated signature (random number from 1
to (count signatures))
.
every item
is equivalent to items 1 through -1
,
except that every
returns an empty list if there are no elements,
instead of an error. See Range for more details.
For historical reasons, Filter is considered its own key form, though it would
be more accurate to think of it as a modifier to index-based key forms (index,
arbitrary, middle,
range, and every). While the filter key form is most often used with a test on a
property of the object (such as files whose name contains "Smith"
),
tests on an element or the object itself are also valid. For example:
paragraphs where it starts with "The" albums where it contains photo "White Rabbit" documents whose first paragraph contains the word "Metacortex"
In theory, any boolean expression should be valid, but AppleScript currently cannot deal with function calls in a filter expression.
If an object under consideration does not have an attribute specified in the
filter, it is not an error – consider the value to be missing
value
and respond appropriately.
Returns a single object whose id
property matches the specified
value. ID data should not honor the current AppleScript considerations, such as
considering case
. Treat IDs as
always case-insensitive, or as always case-sensitive if case is relevant (that is,
if "ijkl"
and "IJKL"
are both valid and different).
Item indicies should match the user’s model of a first-to-last or
front-to-back ordering. (In fact, first
and front
are
synonyms in AppleScript, as are last
and back
.) This means
that window 1
should always be the front window, table 1
should be the first table in the document, and so on. Negative indicies go
backwards from the end, so item -1
and last item
mean
the same thing, item -2
is the second-to-last item, and so on.
If the items are unordered as defined above, then ideally the index should match how the items are currently displayed. Failing that, pick a consistent order – say, alphabetical – and use that.
Returns a single object whose name
property matches the
specified string. String matching should honor the current AppleScript
considerations, such as considering case
or ignoring
diacriticals
. Name data may come to your application as plain or
Unicode text, so be prepared to handle either, even if you do not use full
Unicode internally.
Ranges may be specified as either two indicies or two endpoint objects, which are included in the range. If either endpoint does not exist, or if they are not both in the same container, return an error. Guidelines for the meaning of indicies apply as for the index key form above.
The order of the endpoints does not matter – always return the
objects in order, lowest index to highest. For example, words 8 through
1
should return the same thing as words 1 through 8
.
Text presents a special case: because of how its containment works, it is possible to specify two endpoints where one is inside the other. (Normally, this would be an invalid range, because the two objects would have different containers.) To resolve this, find the beginnings of both endpoints, start from the one closest to the beginning of the text, and then go to the end of the other endpoint object. For example:
set t to "Through three cheese trees three free fleas flew" words from paragraph 1 to word 3 of t --> {"Through", "three", "cheese"} characters from character 10 to word 2 of t --> {"h", "r", "e", "e"}
Range specifiers always return a list, even if the range contains only one
object (or, in the case of every
, no objects).
If a range reference is used as the container for another property or object,
the result is that property or object of every item in the range. For example:
name of files 2 thru 3 of home --> {"Memory report", "BBEdit update.dmg"}
The list of values returned should always parallel the list that would have been returned for the range. That is, it should be in the same order and have the same number of values. For example:
every duck --> {mallard "Daffy", pintail "Marvin", decoy duck "Q-36"} name of every duck --> {"Daffy", "Marvin", "Q-36"}
This applies even if some of the objects do not have the specified property
or element. For such objects, return missing value
. Continuing
the example, consider a feet are webbed
property, given that decoys do
not have feet:
feet are webbed of every duck --> {true, true, missing value}
If the specified element is another range, then the result is a list of lists. For example:
every duck of every flock --> {{mallard "Ned", mallard "Ted"}, {pintail "Ann", pintail "Dan"}}
Relative specifiers are of the form class (before | after)
specifier
, where specifier
may be any
other specifier, including another relative specifier. Therefore,
word after word after word 1
is a perfectly valid way to describe
the third word. This may seem peculiar, but is relevant when working with
text selections: if the selection is an insertion point, say, insertion
point after word 12
the word after
that point would be word after insertion point after word 12
.
A container does not need to define a distinct element relationship for every class it holds – it may imply them by inheritance. If an object contains objects of some class, a specifier may specify any subclass and the request will be restricted to objects of that class. For example, consider the fictitious application DuckTracker:
class duck: …
class canvasback: inherits from duck …
class mallard: inherits from duck …
class pintail: inherits from duck …
class application
element duck by index, by name…
Even though it only explicitly specifies duck
as an element,
a script could ask for every mallard
and receive a list
of all the mallards.
An object in your application can typically be referenced in one of several
ways. For example, window 1
, window "Lister"
, and window id
743
might all refer to the same window. If you need to return an object,
though – say a script asks to get the first window whose name
begins with "L" – you will need to pick a particular key form.
The form you choose is known as a canonical object specifier, and
should be the most
reliable way to refer to that object. That is, it stands the best chance
of referring to the same object later as things change.
The general rule for the form to use is:
id
property, use that. (window id 743
)name
property, use that. (window "Lister"
)window 1
). The index should always be positive.The class of an object specifier should be the actual class of the object.
Continuing the DuckTracker example from above, this would be something like
mallard id 8237
, not duck id 8237
, even though both
refer to the same object.
In most applications, all object relationships are one-to-one or one-to-many,
the result being that all objects are contained by exactly one other object.
Some applications, however, have many-to-one or many-to-many relationships, so
a single object may appear in several different containers. iPhoto behaves
like this: a photo
object may be in any number of albums
.
There is nothing wrong with this model, but it does require certain adjustments.
First, you must support the commands add
and remove
.
These commands are designed to only manipulate containment, as opposed to creating
and destroying objects like make
and delete
do. The
distinction is important because make
and delete
do not have the proper meanings for some tasks. Say you
want to add an existing photo to an album in iPhoto. make
is not really appropriate, since you’re not making anything. Similarly,
say you want to remove a photo from an album. delete
is ambiguous:
will it merely
remove the photo from that album, or will it delete the photo entirely? You
could resolve the problem without different commands by introducing a
photo reference
object, except that there is no such thing in the
user model, and would therefore violate the stay on-task
guideline.
Second, is it possible for your objects (call them photos,
to continue
the iPhoto analogy) to not belong to any specific container (album
)?
For example, in iPhoto, a photo may not be in any album. If this is the case,
then you will need to designate a single master container that holds all the
photos
; they may be added to and removed from albums
from
there. (iPhoto uses application
for this.) If not, then you do
not need a master container, but removing a photo
from its last album
must delete it.
In general, if an object is obtained by a particular property, then the
object’s property ought to match. For example, name of thing
"Bob"
ought to always be "Bob"
, id of thing id
427
ought to always be 427
, and so on.
In some applications, however, this may not be the case. Typically, the
reason is that an object may have more than one valid name (or id, and so on).
For example, the Finder’s application file
class responds to an id
of either the old-style creator type or the new-style bundle id:
application file id "emal"
and application file id
"com.apple.mail"
both return the same thing.
It is perfectly acceptable for multiple key data to return the same object.
(If this creates ambiguities, then see below.) However, you should have some
way for the user to discover all the different data an object can be specified
by. The basic property (for example, name
) should reflect the
canonical data; add other properties for the rest. For example, if a
file’s extension is hidden in the Finder, it will respond to either the
visible name (without the extension) or on-disk name (with the
extension). The on-disk name is considered the canonical name; the
visible name is available via the displayed name
property.
Typically, names and IDs are unique at least within in a container, so there
is only one object folder "Bob"
could possibly return. However,
this is not always the case – for example, in some applications,
names are purely for the user’s reference, and the application makes no
attempt to keep them unique.
For such applications, invoke the be consistent about return types
rule: named and indexed key forms normally return a single object, so they
should still do so even in the face of ambiguous objects. If one object
is a better match than the other, return that one; if there is no best
match, return the first one. However, ensure that users can select all
the matching objects using a filter expression of some sort, such as
every item whose name is ...
There are a few standard classes that applications should define. The only
one that an application must define is application
, the
root of the containment hierarchy. (The root of the inheritance hierarchy,
item
, is defined by AppleScript itself and does not need to
appear in your dictionary.) The properties shown here are merely starting
points; a real application will need to define more, since the ones shown here
do almost nothing. Classes and properties may be omitted if they are not
relevant to an application. For example, not all applications have documents,
or even a selection. Similarly, properties marked here as read-only might
be writable (or vice versa) in some applications.
name text [r/o] -- The name of the application.
frontmost boolean -- Is this the frontmost application?
version text [r/o] -- The short version string of the application.
selection varies -- The current selection; the exact type depends on the application.
element window
element document
application
is the root of the containment hierarchy. That is,
it contains, directly or indirectly, all the objects belonging to the application.
window
elements are only those application windows that contain other
scriptable objects, such as document windows – modal dialogs, sheets,
and formatting or tool palettes should not be included. (Palettes may be included
as properties, however.)
name text [r/o] -- The name of the document.
modified boolean [r/o] -- Does the document have unsaved changes?
file file [r/o] -- The file for the document; may be missing value if the document has never been saved.
An application document. The name
property is user-visible
name, not the on-disk name. name
and file
are
typically read-only; use the save
command to change them.
name text [r/o] -- The title of the window.
bounds rectangle -- The bounding rectangle of the window.
closeable boolean [r/o] -- Can the window be closed?
minimizable boolean [r/o] -- Can the window be minimized?
minimized boolean -- Is the is window minimized?
resizable boolean [r/o] -- Can the window be resized?
visible boolean -- Is the window visible?
zoomable boolean [r/o] -- Can the window be zoomed?
zoomed boolean -- Is the window zoomed?
A window, which may be a document window or palette, but not a sheet or
modal dialog. bounds
is given as a list of four numbers: {top,
left, bottom, right}, with the origin at the top left of the display and x, y
values increasing to the right and down, respectively. (In other words, a
Quickdraw rectangle. This differs from AppleScript Studio, which returns
bounds as {left, bottom, right, top}, with the origin at the bottom left and
x, y values increasing to the right and up.)
visible
refers to whether or not the window is visible as with a
show/hide command, not whether or not the window has been moved to lie mostly
off-screen.
class class [r/o] -- The class of the object.
properties record -- All the properties of the object.
The root of the inheritance hierarchy, and the ultimate ancestor of all
classes. See below for complete descriptions of the class
and
properties
properties. Since item
is defined by
AppleScript, it should not appear in your dictionary. In particular, do not
redefine item
with additional properties or elements –
create your own class with a different name. Classes do not need to explicitly
define that they inherit from item
– if there is no
other parent, item
is assumed.
The class of the object. This should always be the precise class, never
a superclass, and is always read-only. You do not need to explicitly
define class
in your dictionary, since it is defined by
item
, which is the ultimate ancestor of all classes.
Objects in a containment hierarchy may define a container
property that points to their enclosing object. (This property is purely
optional because it is often difficult to implement.) The precise name
depends on the possible container classes: if there is exactly one, name
the property the same as the class. For example, messages in Mail always
belong to a mailbox, and therefore have a mailbox
property.
If there are several possibilities, use the generic term
container
. For example, a file in the Finder may belong to
either a folder or a disk. Do not use the term parent
, since
that refers to the object’s immediate ancestor in the inheritance
hierarchy.
The container property is read-only. Writing to the
container property to move objects is not recommended; use the
move
command instead.
Objects in many-to-x relationships may not have a
single meaningful container. Such objects should omit this property, but
be sure to support specifiers of the form containers where
it contains object
.
If an object’s contents can be represented as a single value, it
should define a contents
property. For example, documents in a
word processor would define a contents
property that returned
all the text as a string. contents
is usually writable.
Recursive containers – that is, objects that have their own class
as elements – should define an entire contents
property,
which returns a flat list containing every descendant element. entire
contents
itself is always read-only, but elements of it may be
writable.
A value that uniquely identifies the object. IDs are never localized and are typically not under the user’s control and therefore are read-only. They should at least be unique within a container – in most applications, they are unique within the entire application – and must remain valid for at least the lifetime of the application process or the object, whichever ends first.
The type of value may be anything: common choices are an integer, a UUID, or a bundle identifier. The type of value for any particular class should always be the same.
An object’s name
property is its user-visible name,
never a system identifier. (Some applications may have a somewhat flexible
notion of what the user-visible name is; see
Non-symmetric Specifiers above.)
name
is usually writable, though in some applications it may
be read-only.
All objects should have a properties
property, which returns
a record containing all the properties of the object. You do not need to
explicitly define properties
in your dictionary, since it is
defined by item
, which is the ultimate ancestor of all classes.
If any properties are writable, then properties
is as well, and can
accept a record containing any number of the properties for that object.
For example:
get the properties of paragraph 4 -- Returns font, size, style, and so on. set the properties of paragraph 4 to {font:"Helvetica", size:14} -- Sets just the font and size.
The properties
property need not include every property of an
object. Typically, properties that can be derived from other properties are not
included, such as the reverse
property of a list.
Commands in AppleScript are defined independently of any particular object; a class may then list the commands that it responds to. To Java and Objective-C programmers, commands therefore resemble protocols more than member functions. There is currently no straightforward way to define a command differently (say, with different parameters) for a particular class. The best you can do is either to list all possible parameters and use the comments to say which parameters are meaningful when, or to define separate commands with different names.
AppleScript commands normally look like imperative English sentences:
verb [noun] [preposition value]... save the front document in file "Goofballs:Razor"
Try to emulate this style with your own commands. However, bear in mind that this is a guideline, so your English style does not have to be perfect. Also, you are by no means obliged to support all possible English sentences. Synonyms are fine where they make sense, but don’t go overboard.
Try to use the standard commands where it makes sense, and only create
your own custom commands if there is a clear need. For example,
Mail’s send
action does not fit into the standard
commands in any obvious way, so it gets its own command. On the
other hand, the Finder does not define a rename
command, because it can simply set the value of the object’s
name
property.
every
return lists.In general, commands that return a result should return the same
shape
of result as the direct parameter specifier: singular specifiers
return bare values, range and every
specifiers return lists.
(There are exceptions to this; see count
and exists
.)
A range specifier where one or both endpoints do not exist is an error;
an every
specifier that specifies no objects is not. This
rule also applies to filtered range and every
specifiers.
For example:
-- Singular specifiers like index return a value. get name of person 1 --> "Fillmore" -- Range and "every" specifiers return a list... get name of every person --> {"Fillmore", "Poppleton"} -- ...even if there is only one object... get name of every person whose name starts with "P" --> {"Poppleton"} -- ...or even no objects for "every"... get name of every person whose name starts with "Q" --> {} -- ...but both endpoints must exist for a range. get name of people 1 through 17 --> Error: Can't get person 17.
Commands that break this rule – for example, given a range they sometimes return a value and sometimes a list – are annoying to use, because they require the user to write logic to find out what happened. See Key Forms for more details.
The subject of the innermost tell
block is the target.
Use the target to complete parameters that the script omits. In some cases,
AppleScript will do this for you: since the target usually corresponds to the
direct parameter, AppleScript will automatically pass the target as the direct
parameter if the command requires one and the script does not supply it. In
some cases, however, the target makes sense as a prepositional parameter.
Consider add x to y
: it would be reasonable to say tell y to
add x
instead. Your application can detect the missing to
parameter and pick the target out of the subject parameter, an extra
parameter that AppleScript passes the target in.
The return value of an AppleScript command should always be the value or object result of the command, never a status code or message. If there is no result – that is, the command only performs an action – return nothing.
To signal an error, return a non-zero error code from your event handler,
or set the kOSAErrorNumber
parameter in the reply. Use standard
error codes where appropriate, in particular the AppleEvent error range from
-1700 to -1799 (described in Result Codes
in
Apple
Event Manager Reference). Use of the additional error parameters such as
kOSAErrorOffendingObject
(see OSA.h for a complete list) is highly
recommended – the idea is to return as specific an error as possible.
For example, say a script passes a list where your command wanted a number:
BAD | Return paramErr → error: Parameter error. |
BETTER | Return errAECoercionFail → error: Can't make some data into the expected type. |
BEST | Return errAECoercionFail ,set kOSAErrorOffendingObject to the given value,set kOSAErrorExpectedType to cNumber → error: Couldn't make {1, 2, 3} into a number. |
The user canceling an operation is considered an error; use
userCanceledErr
(-128) when this happens.
Scripting commands should not require any user interaction, since part of
the point of scripting is to create completely automated functions. It is
acceptable to interact with the user if information was omitted from the
command, such as attempting to save
a never-before-saved
file without specifying a location, but there should always be parameters
available so a script can work without any interaction. Commands should
never ask for confirmation. For example, the empty trash
script
command in the Finder does not ask for confirmation, even though the menu
command does.
Before attempting to interact with the user in response to a scripting
command, call AEInteractWithUser
. If it returns an error, do not
interact – either do without the user interface, or fail. This lets
scripts specify whether or not they want to allow interaction.
Sometimes you may find yourself implementing a command that contains lots of
options, for which you might be tempted to make separate Boolean parameters.
When the number of parameters is small, it looks good to be able to say
with a, b, and c.
Excessive use of this technique, however, can lead
to unwieldy dictionary entries for these events with long lists of parameters.
There are two solutions to this: either make a parameter or parameters that accept a list of enumerators for the option or set of options, or break the command into separate commands with more focused functionality, reducing the number of options for each one.
For example, suppose a statistics package creates a single command to perform any type of analysis with lots of parameters, like this:
analyze data set -- 25 Boolean options
It would be better to split the analysis capability into multiple commands, each with a small group of Boolean parameters, such as
cluster data set
correlate data set
fit curve data set
and so on. If you find yourself with parameters that can never appear together, it’s a sign you may need to break up the command.
If you receive a command with extra parameters you were not expecting, just
ignore them and do not return an error. Similarly, do not bother checking the
keyMissedKeywordAttr
event attribute – it serves no
useful purpose any more, and always returns false in Mac OS X 10.2 and later.
The Standard Suite defines 15 commands. Applications should use these in
preference to creating their own.
Not all the commands are meaningful to all applications. Irrelevant
ones (for example, open
in an application without documents, like
System Preferences) should not appear in your dictionary.
In most cases, parameters of type specifier
can specify any number of
objects. Be sure your commands function correctly with singular, range, and
every
specifiers, and that they follow the ranges return
lists
rule described above.
A few words about location parameters
Several commands take parameters of type location specifier.
This may be an insertion point specifier or a container object specifier,
and may even be a range in most cases.
An insertion point is a location where an object may be
inserted: beginning
, end
, or before
or
after
another specifier. The other specifier may be a range or
every
specifier, in which case the operation is repeated for all
the resulting insertion points.
add photo "On the beach" to end of album "Vacation" move paragraph 1 to after paragraph 3 make new word at beginning of every paragraph with data "Note:"
A container object specifier is a container in which the objects are to be
added, moved, made, or whatever. The application chooses a reasonable location
within the container. Again, the specifier may be a range or every
specifier.
add photo "On the beach" to album "Vacation" duplicate message 1 to every mailbox whose name contains "backup"
Some containers may accept only container specifiers; see Object Specifiers for details.
add specifier [to location specifier]
-- Returns a specifier to the object in its new location.
Adds the specified objects to a (position within a) container, and returns
their new locations. If the
to
parameter is omitted, it is inferred from the target.
(For example, in tell album "Waterfowl" to add photo 4
, the
to
location is the target, album "Waterfowl"
.)
Do not support this command if your application has only one-to-one
and one-to-many relationships;
see Many-to-many
relationships for more details. Use of a range or every
specifier in the to
parameter assumes that an object may be
added to any number of containers; this may not be true for all
applications.
close specifier [saving yes | no | ask]
Closes the specified objects. Documents and windows should be closeable
if they exist; applications may apply close
to other objects as
well, and may add additional parameters.
count specifier
-- Returns the number of specified objects.
Returns the number of specified objects. This command is an exception to the
ranges return lists
rule; count
with a range or every
returns the number of objects specified, which may be zero for
every
specifiers. Attempting to count
non-existent objects is an error.
Using count
with a singular specifier such as by-name is
valid but not very useful, since it will either return 1 or fail if there is no
such object. (Scripts should use exists
in such cases.) For
historical reasons, count
used to define an each
parameter, but it was not necessary and has been deprecated.
delete specifier
Removes the specified objects from any containers they may be in and
destroys them. delete
should always delete the object, no
questions asked. If you want to have a trash of some sort (such as the Finder
does), define a trash
object and let users move
objects to it. Depending on the application model, deleting an object may
cause its container to be deleted as well – it may not be possible
for a container to have no elements.
duplicate specifier
[to location specifier]
[with properties record]
-- Returns a specifier for the new object.
Duplicates the specified objects to the specified locations, optionally
changing some of their properties using the with properties
parameter, and returns specifiers for the new objects. If the
to
location is not specified, create the new objects in the same
container, suitably altered. (For example, duplicate thing "Bob"
might result in a new thing named "Bob copy".) It is not specified
what happens if the new object would create a conflict; an application can
simply fail, or it can resolve the conflict by tweaking the new object, or it
can define additional parameters to specify how to handle conflicts.
exists specifier
-- Returns true if the object exists, false if not.
Returns true if all of the specified objects exist, or false if they do not.
Unlike most commands, specifying non-existent objects is not an
error; it merely means the result is false. Like count
,
exists
is an exception to the ranges return lists
rule;
range specifiers return true if and only if all the objects in the range exist,
every
specifiers return true if at least one such object exists.
get specifier [as class]
-- Returns the value of the specified object.
Returns the value of the specified objects. You do not need to define
get
in your dictionary, since it is built into AppleScript. You
may not redefine get
with different parameters.
make [new] class
[at location specifier]
[with properties record]
[with data anything]
-- Returns a specifier to the new object.
Makes a new object and returns a specifier to it. at
should be
optional. If it is omitted, pick a reasonable default location – the
beginning or end of the current document are common choices.
Ideally, the with
parameters are optional, but one or the other
(but not both) may be required for some classes. Be careful to distinguish
between with properties
and with data
. Use
with properties
when you are overriding default properties on
an application-created object. When the object is effectively defined by
data from an outside source – say, a string or a file –
use with data
, even if one of the created object’s
properties is (a pointer to) that data.
move specifier to location specifier
-- Returns a specifier to the object in its new location.
Moves the specified objects to a new location and returns specifiers for
them in their new locations. The to
parameter is always
required, and may not be a range or every
specifier.
open file | list of files
-- Returns a specifier to the created application object.
Opens the specified files, and returns the new application objects for
them – probably documents
. The standard version
is designed for opening document files, but applications may apply
open
to other objects and add additional parameters.
print specifier
[with properties print settings]
[print dialog boolean]
Prints the specified objects. For most applications, the objects are always
documents, but this is not required. Support for the with properties
and print dialog
parameters was added in Mac OS X 10.3 (Panther);
see TN2082
for details.
quit [saving yes | no | ask]
Quits the application. The value of the saving
parameter, which
defaults to ask
, is passed to any resulting close
commands. Applications that automatically save everything should not define the
saving
parameter.
remove specifier [from specifier]
Removes the specified objects from their container. If the
from
parameter is omitted, it is inferred from the direct
parameter if possible.
(For example, in remove first photo of album 1
, the from
parameter is understood to be album 1
. However, in remove
photo id 437
, it is impossible to tell, and the application should
return an error.)
Do not support this command
if your application has only one-to-one and one-to-many relationships.
remove
may or may not delete the object, depending on
the application model;
see Many-to-many
relationships for details.
Depending on the application model, removing an object may cause
its container to be deleted – it may not be possible
for a container to have no elements.
save specifier [in file]
Saves the specified objects, possibly in a specified file. save
without in
acts like the Save menu command, and may ask the user
for a file location; save in
with an unsaved file acts like Save
As; save in
with an already-saved file acts like Save A
Copy.
select specifier
Selects the specified objects in the user interface. See Scripting the Selection for details on behavior.
set specifier to value
-- Returns the value assigned.
Sets the specified objects to a value and returns the value assigned, that
is, the value of the completely evaluated to
parameter. This is
not necessarily the same thing as the original value of the parameter, in
particular if it was an object specifier. In most applications, the direct
parameter must be a property specifier, since they do not allow setting of
actual objects to values. You do not need to define set
in
your dictionary, since it is built into AppleScript. You may not redefine
set
with different parameters.
After all this talk about scripting the model, not the view, this section probably comes as a bit of a surprise. Nonetheless, a small amount of view-oriented scriptability is valuable. One of things users do with scripting is to add new commands to their applications, and in order to do that effectively, they need to be able to find out what is visible and what is selected.
As mentioned in Objects, windows should be indexed front to back, so
window 1
or first window
is always the frontmost
window, and window -1
or last window
is always the rearmost. This is implicit in the AppleScript language, because
it allows front
and back
as synonyms for
first
and last
, respectively.
If you have more than one kind of window – say, document windows
and palette windows – create different window
subclasses
to handle them. Given a window specifier, a script should be able to determine
what kind of window it is by inspecting the class
property.
Windows that display model objects should have appropriately named
properties to get the object (or objects) that they display. The most common
case of this is document windows, which should have a document
property that points to the document for that window. If there is exactly one
model object for a window, the property should be the implicit subcontainer for
the window. (In sdef terms, define it using a contents
element.)
That way, a script can ask for model elements of a window without having to
explicitly go through the model object property. For example, if
documents
have words
, then get word 1 of window
1
should work. Without the implicit subcontainer, a script would have
to say word 1 of document of window 1
. The reverse relationship
is not recommended; for instance, documents do not have windows as implicit
subcontainers.
activate
commandTo activate a window – that is, to bring it to the
front – use the activate
command and specify a window.
Calling activate
with no direct object should bring the entire
application forward. activate
should also work with objects
that are one-to-one mapped with windows, such as documents. The system will
automatically handle the activate-whole-application
case for you, but applications must handle the other cases themselves.
The selection, in scripting terms, refers only to selected model objects. Do not attempt to represent the selection in dialog boxes. Not all applications need to support scripting the selection – for some very simple ones, such as control panel-like applications, it may not be meaningful.
There are two ways to work with the selection: the select
command, and selection
properties. To find out what is selected,
get the value of the selection
property. To select something,
either select
it, or set the appropriate selection
property.
select
commandselect
, given an object specifier, will select that object
in the user interface and make it active. This means
Whenever you select
something, the appropriate
selection
properties should change to that same something.
selection
propertyThe selection
property is the current selection for that
object. There may be more than one selection
property in
an application:
The application class should always have a selection
property. Setting it behaves as the select
command.
If you have one window per document and each document maintains its
own selection (as, for example, TextEdit), then your document class
should have a selection
property. Setting it changes the
selection for that document, but does not change focus, so a scripter
can change the selection of an inactive window without making it
active.
If you can have multiple windows viewing the same model objects (as,
for example, Mail can), then windows should have a
selection
property. Setting it changes the selection
for that window, as the previous case. If selection is purely a
view concept, then the document class (if any) should not
have a selection
property.
Other objects that have their own selection should also have a selection
property.
As much as possible, selection
properties should always
be the same type of value: always a single object, always a list of objects, or
whatever is appropriate. In an application that has several different kinds of
selectable data, the application’s selection
property will
often change type; this is acceptable. For example, in Mail, the selection
could be a list of messages, a list of addresses, or text.
In a text-based object model, the value of the selection is a specifier
that gives the location of the selected text, not the actual text –
for example, characters 1 through 3 of document 1
, not
"The"
. This allows scripts to change both what is selected and
the selected text (see below), and allows them to speak of positions relative
to the selection. To get the selected text, get the contents of the
selection
.
If the selection is a range of characters, then you should return a range
of some sort, such as characters 1 through 3
. (However, see the
following paragraph.) If the selection is just an insertion point and
contains no characters, then use the insertion point
class,
as shown in the table below.
Some applications are aware enough of the text object model to report the
selection in terms of units larger than characters. For example, if the user
selected the third paragraph, the selection
property would report
paragraph 3
, not characters 173 through 254
;
selecting the second word of that paragraph would yield word 2 of
paragraph 3
. This behavior is encouraged, but not required.
If you support disjoint text selections, then the selection
property should be a single specifier if the selection is not disjoint, but
should be a list of specifiers if it is. This breaks the always be the same
type of value
guideline, but yields less surprising behavior in most
cases.
Now is the time | insertion point before character 1 |
Now is the time | insertion point after character 3 insertion point after word 1 |
Now is the time | characters 1 through 3 word 1 |
Now is the time | {characters 1 through 3, characters 12 through 15} {word 1, word 4} |
(In real life, the various specifiers would continue on with something like
of document 1
. This has been removed for brevity.)
Since writing to the selection property itself merely changes what is
selected, you must write to a property or element of the selection to change
the actual text. As mentioned above, selection
should allow access to
sub-elements – in this case, characters
,
words
, and so on. To get at the whole selection, use a
contents
property. Setting a property or element
changes the text, and may change the length of the selection. For example:
select word 3 |
one two four |
set contents of the selection to "thref" |
one two thref |
set last character of the selection to "e" |
one two three |
The text model allows moving the selection relative to itself by use of
before
and after
specifiers using the selection
as a base. To select a point, use the insertion point
class.
Use of beginning
and end
is also allowed.
select word 1 |
Now is the time |
select word after the selection |
Now is the time |
select insertion point after the selection |
Now is the time |
select beginning |
Now is the time |
select end |
Now is the time |
As stated above, using set the selection to
would have had
the same effect.
Some applications have multiple views in one window, each with its own
selection. For example, viewer windows in Mail can have as many as three
selections: the selected mailboxes, the selected messages within that
mailbox, and the selected text within a message. Only one of them should
have focus, and this determines the value of the selection
property for the application and the window as a whole. (If you have
trouble determining where the focus is, think of it as if I select
Cut or Copy, what data will go onto the clipboard?
)
In such cases, you should add additional selected thing
elements to get at the various sub-selections. For example, Mail would
define the element types selected mailbox
, selected
message
, and selected text
.
If you use the same term in more than one place in your dictionary, all of
them must use the same 4-byte code. For example, if you use input
as a parameter, again as a property, and later as an enumerator, use the same
type code in all three places. Conversely, if you use the same type code in
more than one place, all of them must use the same term. This also applies to
terms and codes inherited from system- or framework-supplied dictionaries.
For example, if you define a color
property for a class, its code
must be 'colr'
, because that is how it is defined in the
AppleScript dictionary and Cocoa’s NSCoreSuite script suite. Failure to
follow this rule will cause your scripts to fail or change in odd ways when
they are compiled or decompiled – AppleScript will change the
original term to be the last one defined.
aete
dictionaries actually exploit breaking this rule in order
to define synonymous terms or codes. sdef provides direct support for synonyms
with its synonym elements; see the sdef documentation for details.
For one thing, basic AppleScript syntax is oriented towards English; having identifiers in another language would be odd. For another, it probably would not work: AppleScript identifiers cannot contain non-ASCII characters, which severely restricts what you can say in most languages.
Cocoa is Apple’s application framework: it is strongly object-oriented, and makes it easy to add scriptability. For documentation, see Scriptable Applications. However, Cocoa’s default scripting support does not follow the scripting interface guidelines in all cases. Fortunately, there are workarounds:
aete
to your application.Cocoa applications currently define their scriptability by providing a set
of script suites. Each script suite is a pair of plist files, a suite
definition (.scriptSuite) and a suite terminology (.scriptTerminology).
Cocoa can generate an aete
format dictionary from these if you
let it, but the result tends to be sub-standard,
because script suites cannot express
certain concepts. (Also, your application will have to be launched to get
the dictionary, which tends to annoy scripters.)
Therefore, you should build a parallel aete
dictionary as part of your application. The easiest way to do this is to write
your dictionary using an sdef, and then generate all three files (.scriptSuite,
.scriptTerminology, and aete.r) from it using sdp
(1).
at
parameter for make
.Because of an early design mistake, Cocoa requires the at
parameter for make
by default. You should always override this
using the LocationRequiredToCreate
attribute – see
the Cocoa
Scripting release notes for more details. If you write your dictionary
using an sdef, sdp
(1) will add the correct attribute for you.
Cocoa Scripting suite definitions have trouble being completely accurate.
Definitions you inherit from the frameworks – in particular, the
Standard and Text Suites – are almost completely generic.
Commands that your application does not support are still visible, direct
parameters are defined as merely type reference
, and
application
has document
elements whether it needs
them or not.
You can work around this by creating
an aete
dictionary for your application (see above), but it
is more work. If you use an sdef to generate your aete
, you will
have to modify the sdef version of the Standard Suite
(/Developer/Examples/Scripting Definitions/NSCoreSuite.sdef
),
since it currently will have the same problem.
Cocoa Scripting’s default error messages are written in generic
programmerese (such as NSReceiverEvaluationScriptError: 4
) rather than
specific English (such as Can’t get window 17
).
As of Mac OS X 10.3 (Panther), you can set your own error numbers and messages
by calling [NSScriptCommand currentCommand]
and then calling
-setScriptErrorNumber:
and -setScriptErrorString:
on the returned object. See the
NSScriptCommand documentation
for further details.
Developer Documentation | Technical Q&As | Development Kits | Sample Code |